﻿using Microsoft.Extensions.Logging;
using System;
using System.Diagnostics.Contracts;

namespace Silky.Core.Logging
{
    public abstract class AbstractSilkyInternalLogger : ISilkyInternalLogger
    {
        private static readonly string EXCEPTION_MESSAGE = "Unexpected exception:";

        /// <summary>
        /// Creates a new instance.
        /// </summary>
        /// <param name="name">A friendly name for the new logger instance.</param>
        protected AbstractSilkyInternalLogger(string name)
        {
            Contract.Requires(name != null);

            this.Name = name;
        }

        public string Name { get; }

        public bool IsEnabled(LogLevel level)
        {
            switch (level)
            {
                case LogLevel.Trace:
                    return this.TraceEnabled;

                case LogLevel.Debug:
                    return this.DebugEnabled;

                case LogLevel.Information:
                    return this.InfoEnabled;

                case LogLevel.Warning:
                    return this.WarnEnabled;

                case LogLevel.Error:
                    return this.ErrorEnabled;

                default:
                    throw new ArgumentOutOfRangeException();
            }
        }

        public abstract bool TraceEnabled { get; }

        public abstract void Trace(string msg);

        public abstract void Trace(string format, object arg);

        public abstract void Trace(string format, object argA, object argB);

        public abstract void Trace(string format, params object[] arguments);

        public abstract void Trace(string msg, Exception t);

        public void Trace(Exception t) => this.Trace(EXCEPTION_MESSAGE, t);

        public abstract bool DebugEnabled { get; }

        public abstract void Debug(string msg);

        public abstract void Debug(string format, object arg);

        public abstract void Debug(string format, object argA, object argB);

        public abstract void Debug(string format, params object[] arguments);

        public abstract void Debug(string msg, Exception t);

        public void Debug(Exception t) => this.Debug(EXCEPTION_MESSAGE, t);

        public abstract bool InfoEnabled { get; }

        public abstract void Info(string msg);

        public abstract void Info(string format, object arg);

        public abstract void Info(string format, object argA, object argB);

        public abstract void Info(string format, params object[] arguments);

        public abstract void Info(string msg, Exception t);

        public void Info(Exception t) => this.Info(EXCEPTION_MESSAGE, t);

        public abstract bool WarnEnabled { get; }

        public abstract void Warn(string msg);

        public abstract void Warn(string format, object arg);

        public abstract void Warn(string format, params object[] arguments);

        public abstract void Warn(string format, object argA, object argB);

        public abstract void Warn(string msg, Exception t);

        public void Warn(Exception t) => this.Warn(EXCEPTION_MESSAGE, t);

        public abstract bool ErrorEnabled { get; }

        public abstract void Error(string msg);

        public abstract void Error(string format, object arg);

        public abstract void Error(string format, object argA, object argB);

        public abstract void Error(string format, params object[] arguments);

        public abstract void Error(string msg, Exception t);

        public void Error(Exception t) => this.Error(EXCEPTION_MESSAGE, t);

        public void Log(LogLevel level, string msg, Exception cause)
        {
            switch (level)
            {
                case LogLevel.Trace:
                    this.Trace(msg, cause);
                    break;

                case LogLevel.Debug:
                    this.Debug(msg, cause);
                    break;

                case LogLevel.Information:
                    this.Info(msg, cause);
                    break;

                case LogLevel.Warning:
                    this.Warn(msg, cause);
                    break;

                case LogLevel.Error:
                    this.Error(msg, cause);
                    break;

                default:
                    throw new ArgumentOutOfRangeException();
            }
        }

        public void Log(LogLevel level, Exception cause)
        {
            switch (level)
            {
                case LogLevel.Trace:
                    this.Trace(cause);
                    break;

                case LogLevel.Debug:
                    this.Debug(cause);
                    break;

                case LogLevel.Information:
                    this.Info(cause);
                    break;

                case LogLevel.Warning:
                    this.Warn(cause);
                    break;

                case LogLevel.Error:
                    this.Error(cause);
                    break;

                default:
                    throw new ArgumentOutOfRangeException();
            }
        }

        public void Log(LogLevel level, string msg)
        {
            switch (level)
            {
                case LogLevel.Trace:
                    this.Trace(msg);
                    break;

                case LogLevel.Debug:
                    this.Debug(msg);
                    break;

                case LogLevel.Information:
                    this.Info(msg);
                    break;

                case LogLevel.Warning:
                    this.Warn(msg);
                    break;

                case LogLevel.Error:
                    this.Error(msg);
                    break;

                default:
                    throw new ArgumentOutOfRangeException();
            }
        }

        public void Log(LogLevel level, string format, object arg)
        {
            switch (level)
            {
                case LogLevel.Trace:
                    this.Trace(format, arg);
                    break;

                case LogLevel.Debug:
                    this.Debug(format, arg);
                    break;

                case LogLevel.Information:
                    this.Info(format, arg);
                    break;

                case LogLevel.Warning:
                    this.Warn(format, arg);
                    break;

                case LogLevel.Error:
                    this.Error(format, arg);
                    break;

                default:
                    throw new ArgumentOutOfRangeException();
            }
        }

        public void Log(LogLevel level, string format, object argA, object argB)
        {
            switch (level)
            {
                case LogLevel.Trace:
                    this.Trace(format, argA, argB);
                    break;

                case LogLevel.Debug:
                    this.Debug(format, argA, argB);
                    break;

                case LogLevel.Information:
                    this.Info(format, argA, argB);
                    break;

                case LogLevel.Warning:
                    this.Warn(format, argA, argB);
                    break;

                case LogLevel.Error:
                    this.Error(format, argA, argB);
                    break;

                default:
                    throw new ArgumentOutOfRangeException();
            }
        }

        public void Log(LogLevel level, string format, params object[] arguments)
        {
            switch (level)
            {
                case LogLevel.Trace:
                    this.Trace(format, arguments);
                    break;

                case LogLevel.Debug:
                    this.Debug(format, arguments);
                    break;

                case LogLevel.Information:
                    this.Info(format, arguments);
                    break;

                case LogLevel.Warning:
                    this.Warn(format, arguments);
                    break;

                case LogLevel.Error:
                    this.Error(format, arguments);
                    break;

                default:
                    throw new ArgumentOutOfRangeException();
            }
        }

        public override string ToString() => this.GetType().Name + '(' + this.Name + ')';
    }
}